iT邦幫忙

2024 iThome 鐵人賽

DAY 27
0
Modern Web

一起來玩圖像編輯器:Fabric.js 的實戰修煉系列 第 27

Day27-fabric.js + React 的重構血淚三部曲(之一)-要怎麼在 React 裡使用 fabric.js

  • 分享至 

  • xImage
  •  

以下,來說說故事,這是一個使用 fabric.js + Next.js(React) 製作的專案
剛開始對技術選用與 React、fabric.js 底層理解都不夠深,過程處理可能聽起來不太聰明,但都是我走過的路QAQ

結果未必是最好的解法。單純經驗分享,如果有更好的方式都歡迎討論~


初期的時候,我也是像前面介紹的那些功能那樣把需求的功能一個一個拼起來。
在無框架環境上,這些看起來是單純些。
在單純環境下,知道這些功能怎麼運作、組裝。

但是,我們的需求是要做在 next.js / React 的專案裡面
看到人家用

'use client';

import React, { useRef, useEffect } from 'react';
import { fabric } from 'fabric';

const FabricCanvas = () => {
	const fabricRef = useRef<null | fabric.Canvas>(null);
	const canvasRef = useRef<null | HTMLCanvasElement>(null);

	useEffect(() => {
		const initFabric = () => {
			if (!canvasRef.current) return;
			fabricRef.current = new fabric.Canvas(canvasRef?.current, {
				width: 800,
				height: 600,
				backgroundColor: '#fff',
			});
			fabricRef.current.renderAll();
		};


		initFabric();

		const disposeFabric = () => {
			fabricRef?.current?.dispose();
		};

		return () => {
			disposeFabric();
		};
	}, []);

	return <canvas ref={canvasRef} />;
};

export default FabricCanvas;

然後才能引入 FabricCanvas 到頁面來用

import FabricCanvas from './FabricCanvas';

const page = () => {
	return (
		<div>
			<FabricCanvas />
		</div>
	);
};

export default page;

當時覺得很複雜而且有很多東西要處理,所以就去找了別人的套件來用(掩面)
殊不知,這是錯誤的開始...

引入(不適合的)套件 fabricjs-react

fabricjs-react - npm (npmjs.com)

哇,看起來變得簡單多了

直接引入,畫布會幫你從 <FabricJSCanvas /> 這裡做好,還有 onReady 供你在畫布準備好時做一些想做的處理
https://ithelp.ithome.com.tw/upload/images/20240830/20168354DJhacIckXZ.png

警語:
使用套件前請先了解套件內容的運作方式,不要無腦使用套件。
套件本身沒有錯,錯的是把它用在不符合需求,而需要大改才好用的地方。

現在回來看,這套件適合在很簡單、快速創建簡單功能的地方使用。
仔細看他除了 canvas 外,還幫你寫了一些現成可用,不用再另外設參數就可以使用的 api
https://ithelp.ithome.com.tw/upload/images/20240830/20168354btuZCA6OjA.png
(據我的理解) 他的 editor 這個參數,是用 state 的方式儲存,並用 hook 的方式來集中管理處理 editor 的 function

但把這些放到 next.js / React 的環境裡面運作、拆分到不同 component 使用時,問題就出現了。


如何在很多不同 component 中傳送畫布變數

一開始想用 Redux 來做整個畫布的狀態管理,想說有多種狀態,Redux 比較好處理,
但在把整個畫布(也就是套件裡的 editor)存進 Redux 時卻一直出現一個錯誤訊息

A non-serializable value was detected in the state...

non-serializable 不可序列化?是什麼意思

Redux 的不可序列化

來舉個例子:
Redux 就像一個堅持只運輸標準箱子的搬家公司:

  • 只接受可以裝入標準箱子,不會動的物品
  • 拒絕運輸活體植物、寵物或正在運行的電器
為什麼要這樣做?
  1. 效率:標準化的箱子更容易堆疊和運輸
  2. 安全:避免運輸過程中的意外(比如寵物逃跑)
  3. 可追蹤:每個箱子都可以編號和追蹤
  4. 可恢復:如果出問題,可以根據箱子清單重新安排物品

所以,像你的狗狗就會被搬家公司拒絕運輸,因為他無法保證狗狗不會亂動亂跑或是大便、偷咬東西的行為。

這裡的狗狗就很像我們的畫布本體,可能會在搬家公司非預期的時候改變。
所以被 Redux 以不可序列化(大概等於 不可被裝進靜止箱子裡,並且無法控制此物件的改變)來拒絕我們。


好的,回到我們的畫布

實際分析畫布不可序列化原因

  1. Redux 的設計原則:
    • Redux 要求 store 中的 state 必須是可序列化的。
    • 這意味著 state 應該可以被轉換為 JSON 格式。
  2. Fabric.js Canvas 的特性:
    • Fabric.js 的 Canvas 對象包含了複雜的內部結構和方法。
    • 這些結構和方法無法被直接序列化為 JSON。
  3. 循環引用:
    • Fabric.js 對象可能包含循環引用,這在序列化過程中會造成問題。
  4. 函數和 DOM 元素:
    • Canvas 對象可能包含函數和 DOM 元素引用,這些都不能被序列化。

好吧,既然不能用 Redux 來存畫布,那畫布的部分我改成用 useContext 來存吧

未完待續...明天繼續


上一篇
Day26-所以說 fabric.js 到底可以做出什麼東西?-別人的作品賞析
下一篇
Day28-fabric.js + React 的重構血淚三部曲(之二)-為什麼我的 component 瘋狂重新 render
系列文
一起來玩圖像編輯器:Fabric.js 的實戰修煉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言